const path = require('path')
const types = require('@babel/types')
const generator = require('@babel/generator').default
const traverse = require('@babel/traverse').default

class NormalModule {
  constructor(data) {
    this.context = data.context
    this.name = data.name
    this.entry = data.entry
    this.moduleId = data.moduleId
    this.rawRequest = data.rawRequest
    this.parser = data.parser
    this.resource = data.resource
    this._source
    this._ast
    this.dependencies = [] // 存放被加载依赖的模块信息
  }

  build(compilation, callback) {
    /**
     * 1. 从文件中读取到将来需要被加载的module内容
     * 2. 如果当前不是 js 模块，则需要Loader进行处理，最终返回 js 模块
     * 3. 将 js 代码转换为 ast 语法树
     * 4. 当前 js 模块如果引用了其他模块，则需要递归对其他模块处理
     * 5. 重复执行伤处过程
     */

    this.doBuild(compilation, err => {
      this._ast = this.parser.parse(this._source)
      // 遍历语法树，修改代码
      traverse(this._ast, {
        CallExpression: (nodePath) => {
          let node = nodePath.node
          if (node.callee.name === 'require') {
            let modulePath = node.arguments[0].value
            let moduleName = modulePath.split(path.posix.sep).pop()
            let extName = moduleName.indexOf('.') === -1 ? '.js' : ''
            moduleName += extName
            let depResource = path.posix.join(path.posix.dirname(this.resource), moduleName) // 依赖模块的绝对路径
            let depModuleId = './' + path.posix.relative(this.context, depResource)  // 获得依赖模块的id

            this.dependencies.push({
              name: this.name,
              context: this.context,
              rawRequest: moduleName, // 模块名称
              moduleId: depModuleId,
              resource: depResource // 模块路径
            })

            // 替换内容
            node.callee.name = '__webpack_require__'
            node.arguments = [types.stringLiteral(depModuleId)]
          }
        }
      })

      // 将修改后的ast转换为js代码
      let { code } = generator(this._ast)

      this._source = code
      callback(err)
    })
  }

  doBuild(compilation, callback) {
    this.getSource(compilation, (err, source) => {
      this._source = source
      callback(err)
    })
  }

  getSource(compilation, callback) {
    compilation.inputFileSystem.readFile(this.resource, 'utf8', callback)
  }
}

module.exports = NormalModule
